/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <hal_i2c.h>
/* includes (standard library, system) ---------------------------------------*/
#include <limits.h>
#include <string.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_common.h>
#include <x_it.h>
#include <x_warn.h>
#include <x_bug.h>
#include <x_errno.h>

#ifdef CONFIG_DEVFS
#include <devfs.h>
#endif

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/**
 * hal_i2c_open - open a i2c bus by name
 * @name: the i2c bus name, like "i2c1", "i2c2" etc
 *
 * return: a pointer to the i2c control struct or NULL if device not found
 */
hal_i2c_t *hal_i2c_open(const char *name)
{
    WARN_ON(name == NULL, errno = ERRNO_HAL_I2C_NO_MATCH; return NULL, "Invalid name!");

    /*
     * parent is just the first member of hal_uart_t, so we can return this even when
     * hal_open return NULL
     */

    return container_of(hal_open(name, 0), hal_i2c_t, parent);
}

/**
 * hal_i2c_close - close a i2c bus by name
 * @i2c: the i2c bus
 *
 * return: 0, always for now
 */
int hal_i2c_close(hal_i2c_t *i2c)
{
    BUG_ON(i2c == NULL, "Invalid i2c bus!");

    return hal_close(&i2c->parent);
}

/**
 * hal_i2c_register - register a i2c hal bus
 * @i2c:  the i2c bus to register
 * @name: the i2c bus name, like "i2c1", "i2c2" etc
 *
 * return: 0 on success, negative value on error
 */
int hal_i2c_register(hal_i2c_t *i2c, const char *name, const hal_i2c_methods_t *methods,
                     uintptr_t data)
{
    int ret = 0;

    BUG_ON(i2c == NULL, "Invalid i2c bus!");
    BUG_ON(name == NULL, "Invalid i2c bus name!");
    BUG_ON(methods == NULL, "Invalid i2c bus methods!");
    BUG_ON(methods->xfer == NULL, "Invalid i2c bus methods!");
    BUG_ON(methods->recv == NULL, "Invalid i2c bus methods!");

    if (k_mutex_init(&i2c->lock) != 0) {
        return -1;
    }

    i2c->parent.name = name;
    i2c->methods = methods;

    if ((ret = hal_register(&i2c->parent)) != 0) {
        return ret;
    }

    return ret;
}

static int __i2c_ioctl(hal_i2c_t *i2c, int cmd, va_list valist)
{
    int ret;

    if (i2c == NULL) {
        return -EINVAL;
    }
    if (i2c->methods->ioctl == NULL) {
        return -ENOSYS;
    }
    if (unlikely(k_mutex_lock(&i2c->lock) != 0)) {
        return -EBUSY;
    }
    ret = i2c->methods->ioctl(i2c, cmd, valist);
    k_mutex_unlock(&i2c->lock);

    return ret;
}

int hal_i2c_ioctl(hal_i2c_t *i2c, int cmd, ...)
{
    va_list valist;
    int ret;

    va_start(valist, cmd);
    ret = __i2c_ioctl(i2c, cmd, valist);
    va_end(valist);

    return ret;
}

/**
 * hal_i2c_xfer - transfer data to i2c address
 * @i2c:  the i2c channel
 * @addr: the i2c slave device address
 * @data: the data buffer
 * @size: the size of data buffer
 *
 * return: 0 on success, negative value on error
 */
int hal_i2c_xfer(hal_i2c_t *i2c, uint16_t addr, const uint8_t *data, uint16_t size)
{
    int ret;

    WARN_ON(i2c == NULL, errno = ERRNO_HAL_I2C_ILLEGAL_ID; return -1, "Invalid I2C bus!");

    if (unlikely(k_mutex_lock(&i2c->lock) != 0)) {
        return -1;
    }

    ret = i2c->methods->xfer(i2c, addr, data, size);

    k_mutex_unlock(&i2c->lock);

    return ret;
}

/**
 * hal_i2c_recv - recieve data from a i2c channel
 * @i2c:  the i2c channel
 * @addr: the i2c slave device address
 * @data: the data buffer
 * @size: the size of data wanted
 *
 * return: 0 on success, negative value on error
 */
int hal_i2c_recv(hal_i2c_t *i2c, uint16_t addr, uint8_t *data, uint16_t size)
{
    int ret;

    WARN_ON(i2c == NULL, errno = ERRNO_HAL_I2C_ILLEGAL_ID; return -1, "Invalid I2C bus!");

    if (unlikely(k_mutex_lock(&i2c->lock) != 0)) {
        return -1;
    }

    ret = i2c->methods->recv(i2c, addr, data, size);

    k_mutex_unlock(&i2c->lock);

    return ret;
}

int hal_i2c_write(hal_i2c_t *i2c, uint16_t addr, uint16_t memaddr, uint8_t masize,
                  const uint8_t *data, uint16_t size)
{
    int ret;

    WARN_ON(i2c == NULL, errno = ERRNO_HAL_I2C_ILLEGAL_ID; return -1, "Invalid I2C bus!");

    if (unlikely(k_mutex_lock(&i2c->lock) != 0)) {
        return -1;
    }

    ret = i2c->methods->write(i2c, addr, memaddr, masize, data, size);

    k_mutex_unlock(&i2c->lock);

    return ret;
}

int hal_i2c_read(hal_i2c_t *i2c, uint16_t addr, uint16_t memaddr, uint8_t masize, uint8_t *data,
                 uint16_t size)
{
    int ret;

    WARN_ON(i2c == NULL, errno = ERRNO_HAL_I2C_ILLEGAL_ID; return -1, "Invalid I2C bus!");

    if (unlikely(k_mutex_lock(&i2c->lock) != 0)) {
        return -1;
    }

    ret = i2c->methods->read(i2c, addr, memaddr, masize, data, size);

    k_mutex_unlock(&i2c->lock);

    return ret;
}

#ifdef CONFIG_DEVFS
static int __i2c_open(uintptr_t data)
{
    hal_i2c_dev_t *dev = (hal_i2c_dev_t *)data;

    return hal_dev_open(&dev->parent, 0);
}

static int __i2c_close(uintptr_t data)
{
    hal_i2c_dev_t *dev = (hal_i2c_dev_t *)data;

    return hal_dev_close(&dev->parent);
}

static int __i2c_read(uintptr_t data, char *buff, size_t nbyte)
{
    hal_i2c_dev_t *dev = (hal_i2c_dev_t *)data;

    return hal_i2c_recv(dev->i2c, dev->addr, (uint8_t *)buff, (uint16_t)nbyte);
}

static int __i2c_write(uintptr_t data, const char *buff, size_t nbyte)
{
    hal_i2c_dev_t *dev = (hal_i2c_dev_t *)data;

    return hal_i2c_xfer(dev->i2c, dev->addr, (const uint8_t *)buff, (uint16_t)nbyte);
}

static const struct devfs_ops __i2c_ops = {
    __i2c_open, __i2c_close, __i2c_read, __i2c_write, NULL, NULL, NULL, NULL, NULL,
};
#endif

/**
 * hal_i2c_dev_register - i2c slave device register
 * @dev:  the device object
 * @bus:  the bus name the device attached to
 * @name: the i2c slave device name
 * @addr: the address of the slave device
 *
 * return: 0 on success, negative value on error
 */
int hal_i2c_dev_register(hal_i2c_dev_t *dev, const char *bus, const char *name, uint16_t addr)
{
    int ret;
    hal_i2c_t *i2c = hal_i2c_open(bus);

    BUG_ON(i2c == NULL, "Invalid I2C device!");

    dev->parent.name = name;
    dev->i2c = i2c;
    dev->addr = addr;

    if ((ret = hal_register(&dev->parent)) != 0) {
        hal_i2c_close(i2c);
        return ret;
    }

#ifdef CONFIG_DEVFS
    return devfs_add_file(name, &__i2c_ops, (uintptr_t)dev);
#else
    return 0;
#endif
}

/**
 * hal_i2c_dev_open - open a i2c device by name
 * @name: the slave i2c device name
 *
 * return: a pointer to the i2c control struct or NULL if device not found
 */
hal_i2c_dev_t *hal_i2c_dev_open(const char *name)
{
    hal_i2c_dev_t *dev;

    WARN_ON(name == NULL, return NULL, "Invalid name!");

    dev = container_of(hal_open(name, 0), hal_i2c_dev_t, parent);
    if (dev != NULL) {
        return dev;
    }

    errno = ERRNO_HAL_I2C_NO_MATCH;

    WARN("Fail to open device \"%s\"!", name);

    return NULL;
}

int hal_i2c_dev_ioctl(hal_i2c_dev_t *dev, int cmd, ...)
{
    WARN_ON(dev == NULL, return -1, "Invalid I2C device!");

    va_list valist;
    int ret;

    va_start(valist, cmd);
    ret = __i2c_ioctl(dev->i2c, cmd, valist);
    va_end(valist);

    return ret;
}

/**
 * hal_i2c_dev_xfer - transfer data to i2c slave device
 * @dev:  the slave i2c device
 * @data: the data buffer
 * @size: the size of data buffer
 *
 * return: 0 on success, negative value on error
 */
int hal_i2c_dev_xfer(hal_i2c_dev_t *dev, const uint8_t *data, uint16_t size)
{
    WARN_ON(dev == NULL, return -1, "Invalid I2C device!");

    return hal_i2c_xfer(dev->i2c, dev->addr, data, size);
}

/**
 * hal_i2c_dev_recv - recieve data from a i2c slave device
 * @dev:  the slave i2c device
 * @data: the data buffer
 * @size: the size of data wanted
 *
 * return: 0 on success, negative value on error
 */
int hal_i2c_dev_recv(hal_i2c_dev_t *dev, uint8_t *data, uint16_t size)
{
    WARN_ON(dev == NULL, return -1, "Invalid I2C device!");

    return hal_i2c_recv(dev->i2c, dev->addr, data, size);
}

int hal_i2c_dev_write(hal_i2c_dev_t *dev, uint16_t memaddr, const uint8_t *data, uint16_t size)
{
    WARN_ON(dev == NULL, return -1, "Invalid I2C device!");

    return hal_i2c_write(dev->i2c, dev->addr, memaddr, dev->masize, data, size);
}

int hal_i2c_dev_read(hal_i2c_dev_t *dev, uint16_t memaddr, uint8_t *data, uint16_t size)
{
    WARN_ON(dev == NULL, return -1, "Invalid I2C device!");

    return hal_i2c_read(dev->i2c, dev->addr, memaddr, dev->masize, data, size);
}
